package com.xuecheng.content.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xuecheng.base.exception.CommonError;
import com.xuecheng.base.exception.XueChengPlusException;
import com.xuecheng.base.model.RestResponse;
import com.xuecheng.content.config.MultipartSupportConfig;
import com.xuecheng.content.feignclient.media.MediaFileClient;
import com.xuecheng.content.mapper.CourseAuditMapper;
import com.xuecheng.content.mapper.CoursePublishMapper;
import com.xuecheng.content.mapper.CoursePublishPreMapper;
import com.xuecheng.content.model.dto.CourseBaseInfoDto;
import com.xuecheng.content.model.dto.CoursePreviewDto;
import com.xuecheng.content.model.dto.TeachplanDto;
import com.xuecheng.content.model.po.*;
import com.xuecheng.content.service.*;
import com.xuecheng.media.model.po.MediaFiles;
import com.xuecheng.messagesdk.model.po.MqMessage;
import com.xuecheng.messagesdk.service.MqMessageService;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;

import java.io.*;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class CoursePublishServiceImpl extends ServiceImpl<CoursePublishMapper,
        CoursePublish> implements CoursePublishService {
    @Autowired
    CourseBaseInfoService courseBaseInfoService;

    @Autowired
    TeachplanService teachplanService;

    @Autowired
    CourseMarketService courseMarketService;

    @Autowired
    CourseTeacherService courseTeacherService;

    @Autowired
    CoursePublishPreMapper coursePublishPreMapper;

    @Autowired
    CourseAuditMapper courseAuditMapper;

    @Autowired
    MqMessageService mqMessageService;

    @Autowired
    CoursePublishMapper coursePublishMapper;

    @Autowired
    MediaFileClient mediaFileClient;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedissonClient redissonClient;

    /**
     * 获取课程预览信息
     * @param courseId
     */
    @Override
    public CoursePreviewDto getCoursePreviewDto(Long courseId) {

        CourseBaseInfoDto courseBase =
                courseBaseInfoService.getCourseBaseInfoDtoById(courseId);
        List<TeachplanDto> teachplans =
                teachplanService.queryAllTeachplansByCourseId(courseId);
        CoursePreviewDto dto = new CoursePreviewDto();
        dto.setCourseBase(courseBase);
        dto.setTeachplans(teachplans);
        return dto;
    }

    /**
     * 课程提交审核
     * @param companyId
     * @param courseId
     * @return
     */
    @Override
    @Transactional
    public RestResponse<String> courseCommitAudit(Long companyId, Long courseId) {
        //首先判断是否是课程所属机构在操作
        CourseBaseInfoDto courseBaseInfoDto =
                courseBaseInfoService.getCourseBaseInfoDtoById(courseId);
        if(courseBaseInfoDto == null){
            return RestResponse.validfail("当前课程不存在");
        }
        if(!companyId.equals(courseBaseInfoDto.getCompanyId())){
            XueChengPlusException.cast("当前课程不属于本机构，无权操作");
        }
        if(courseBaseInfoDto.getAuditStatus().equals("202003")){
            XueChengPlusException.cast("当前课程已经提交了一次审核申请，且在审核中，无法再次提交");
        }
        if(StringUtils.isEmpty(courseBaseInfoDto.getPic())){
            XueChengPlusException.cast("提交失败，请上传课程图片");
        }
        //查询其他课程相关信息
        CourseMarket courseMarket = courseMarketService.getById(courseId);
        List<TeachplanDto> teachplans = teachplanService.queryAllTeachplansByCourseId(courseId);
        if(teachplans.isEmpty()){
            XueChengPlusException.cast("提交失败，未添加课程计划");
        }
        List<CourseTeacher> teachers = courseTeacherService.list(new LambdaQueryWrapper<CourseTeacher>()
                .eq(CourseTeacher::getCourseId, courseId));
        CoursePublishPre coursePublishPre = new CoursePublishPre();
        BeanUtils.copyProperties(courseBaseInfoDto,coursePublishPre);
        coursePublishPre.setMarket(JSON.toJSONString(courseMarket));
        coursePublishPre.setTeachplan(JSON.toJSONString(teachplans));
        coursePublishPre.setTeachers(JSON.toJSONString(teachers));
        coursePublishPre.setStatus("202003");
        //然后将课程信息写入预发布表中
        //先看当前课程的预发布信息是否存在，如果存在，就更新，不存在就新增。
        //虽然只能上次的提交在审核后才能进行下一次提交，但有种情况是上次提交审核通过了，
        //可以发布，还在预发布表中，这个时候课程修改了，又重新提交审核，这下要把之前的提交更新
        CoursePublishPre coursePublishPre1 = coursePublishPreMapper.selectById(courseId);
        if(coursePublishPre1 == null){
            coursePublishPreMapper.insert(coursePublishPre);
        }else{
            coursePublishPreMapper.updateById(coursePublishPre);
        }
        //然后将课程的审核状态改为已提交
        courseBaseInfoDto.setAuditStatus("202003");
        //用updateById方法更新时，可以传递子类对象，只要对应的表中有相关字段就行
        courseBaseInfoService.updateById(courseBaseInfoDto);
        return RestResponse.success("审核提交成功");
    }

    /**
     * 课程审核功能
     * @param courseId
     * @param pass
     * @return
     */
    @Override
    @Transactional
    public RestResponse<Boolean> courseAudit(Long courseId, boolean pass) {
        //审核是平台运营人员审核教学机构的，所以这里不需要判断机构id
        CourseBase courseBase = courseBaseInfoService.getById(courseId);
        if(courseBase == null){
            XueChengPlusException.cast("当前课程不存在");
        }
        String auditStatus = pass ? "202004" : "202001";
        String publishStatus = pass ? "203001" : null;
        courseBase.setAuditStatus(auditStatus);
        courseBase.setStatus(publishStatus);
        courseBaseInfoService.updateById(courseBase);
        if(pass){
            //如果通过，就更新预发布表的状态
            CoursePublishPre coursePublishPre = coursePublishPreMapper.selectById(courseId);
            coursePublishPre.setStatus(auditStatus);
            coursePublishPreMapper.updateById(coursePublishPre);
        }else{
            //如果不通过，就删除预发布表中的对应记录
            coursePublishPreMapper.deleteById(courseId);
        }
        //然后要向课程审核记录表中插入记录
        CourseAudit courseAudit = new CourseAudit();
        courseAudit.setCourseId(courseId);
        courseAudit.setAuditStatus(auditStatus);
        courseAudit.setAuditDate(LocalDateTime.now());
        courseAuditMapper.insert(courseAudit);
        return RestResponse.success(true);
    }

    /**
     * 课程发布功能
     * @param companyId
     * @param courseId
     * @return
     */
    @Override
    @Transactional
    public RestResponse<Boolean> coursePublish(Long companyId, Long courseId) {
        CoursePublishPre coursePublishPre = coursePublishPreMapper.selectById(courseId);
        if(coursePublishPre == null){
            XueChengPlusException.cast("课程未提交审核");
        }
        if(!companyId.equals(coursePublishPre.getCompanyId())){
            XueChengPlusException.cast("当前课程不属于本机构");
        }
        if(!"202004".equals(coursePublishPre.getStatus())){
            XueChengPlusException.cast("必须要先审核通过后才能发布课程");
        }
        coursePublishPre.setStatus("203002");
        saveCoursePublish(courseId);
        //然后向消息表写任务
        saveCoursePublishMessage(courseId);
        //最后删除预发布表中的相关记录
        coursePublishPreMapper.deleteById(courseId);
        return RestResponse.success(true);
    }

    /**
     * @description 保存课程发布信息
     * @param courseId  课程id
     * @return void
     * @author Mr.M
     * @date 2022/9/20 16:32
     */
    private void saveCoursePublish(Long courseId){
        CoursePublishPre coursePublishPre = coursePublishPreMapper.selectById(courseId);
        if(coursePublishPre == null){
            XueChengPlusException.cast("课程预发布数据为空");
        }
        CoursePublish coursePublish = new CoursePublish();
        BeanUtils.copyProperties(coursePublishPre,coursePublish);
        coursePublish.setOnlineDate(new Date());
        coursePublish.setStatus("203002");
        CoursePublish byId = getById(courseId);
        if(byId == null){
            coursePublishMapper.insert(coursePublish);
        }else{
            coursePublishMapper.updateById(coursePublish);
        }
        CourseBase courseBase = courseBaseInfoService.getById(courseId);
        courseBase.setStatus("203002");
        courseBaseInfoService.updateById(courseBase);
    }

    /**
     * @description 保存消息表记录，稍后实现
     * @param courseId  课程id
     * @return void
     * @author Mr.M
     * @date 2022/9/20 16:32
     */
    private void saveCoursePublishMessage(Long courseId){
        MqMessage message = mqMessageService.addMessage("course_publish", courseId.toString(),
                null, null);
        if(message == null){
            XueChengPlusException.cast(CommonError.UNKOWN_ERROR);
        }
    }

    /**
     * 课程静态化
     * @param courseId
     * @return
     */
    @Override
    public File generateCourseHtml(Long courseId) {
        //通过Configuration获取模版
        Configuration configuration = new Configuration(Configuration.getVersion());
        //拿到classpath
        String classpath = this.getClass().getResource("/").getPath();
        File file = null;
        try {
            configuration.setDirectoryForTemplateLoading(new File(
                    classpath + "/templates/"));
            //指定编码
            configuration.setDefaultEncoding("utf-8");
            //获取模版
            Template template = configuration.getTemplate("course_template.ftl");
            //准备数据
            CoursePreviewDto coursePreviewDto = getCoursePreviewDto(courseId);
            Map<String,Object> map = new HashMap<>();
            map.put("model",coursePreviewDto);
            //页面静态化,返回的是字符串，这里其实就是html代码
            String html = FreeMarkerTemplateUtils.processTemplateIntoString(
                    template,map);
            InputStream inputStream = IOUtils.toInputStream(html,"utf-8");
//            file = new File("D:\\minioDownload\\" + courseId + ".html");
            file = new File("E:\\minioDownload\\" + courseId + ".html");
            FileOutputStream outputStream = new FileOutputStream(file);
            IOUtils.copy(inputStream,outputStream);
        } catch (Exception e) {
            log.error("课程静态化异常,课程id：{}",courseId);
            throw new RuntimeException(e);
        }
        return file;
    }

    /**
     * 上传课程静态化页面
     * @param courseId
     * @param file
     */
    @Override
    public void uploadCourseHtml(Long courseId, File file) {
        MediaFiles upload = mediaFileClient.upload(MultipartSupportConfig.getMultipartFile(file),
                "course/" + courseId + ".html");
        if(upload == null){
            log.info("上传静态资源失败，课程id：{}",courseId);
            XueChengPlusException.cast("上传静态资源失败");
        }
    }

    /**
     * 根据课程id查询课程发布信息
     * @param courseId
     * @return
     */
    @Override
    public CoursePublish getCoursePublishById(Long courseId) {
        //先从缓存中查，如果缓存中不存在，就从数据库中查，然后将数据再写入缓存
        String key = "content:course:publish:" + courseId;
        String json = stringRedisTemplate.opsForValue().get(key);
        if(json != null){
            //1.如果json为null或"null"，那么该函数返回null。
            //2.如果json为合法的CoursePublish对象的json格式，则返回对应的对象
            //3.如果json为""空字符串或者是格式错误的不合法的json字符串，则报错
            return JSON.parseObject(json,CoursePublish.class);
        }
        //获取锁
        String lockKey = "lock:" + key;
        RLock lock = redissonClient.getLock(lockKey);
        boolean isLock = false;
        try{
            isLock = lock.tryLock(2, 3, TimeUnit.SECONDS);
        }catch (InterruptedException e){
            throw new RuntimeException("获取分布式锁的时候被中断");
//            XueChengPlusException.cast("redisson获取锁异常，查询课程发布缓存异常");
        }
        if(!isLock){
            XueChengPlusException.cast("查询课程系统繁忙，请稍后再试");
        }
        //成功拿到锁
        //再次查询缓存，双检锁
        CoursePublish coursePublish = null;
        try{
            json = stringRedisTemplate.opsForValue()
                    .get(key);
            if(json != null){
                return JSON.parseObject(json,CoursePublish.class);
            }
            //然后再从数据库中查询
            coursePublish = getById(courseId);
            //然后将数据写入缓存
            //如果coursePublish为null，该序列化方法不会报错，而是返回"null"字符串
            json = JSON.toJSONString(coursePublish);
            stringRedisTemplate.opsForValue().set(key,json,30,TimeUnit.MINUTES);
        }finally{
            lock.unlock();
        }
        return coursePublish;
    }
}
